package info.motteke.kurumi;

import info.motteke.kurumi.AnimationImage.Frame;
import info.motteke.util.LCM;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class AnimationTimer<I> implements Runnable {

    private static final Log log = LogFactory.getLog(AnimationTimer.class);

    private final List<AnimationListener<I>> listeners = new ArrayList<AnimationListener<I>>();

    private final List<AnimationImage<I>> images = new ArrayList<AnimationImage<I>>();

    private List<Task<I>> taskset;

    private volatile int index = 0;

    private volatile boolean running = false;

    private final Thread thread = new Thread(this);

    public void start() {
        if (running) {
            throw new IllegalStateException();
        }

        taskset = createTaskSet();

        running = true;

        log.info("スレッドを開始します。");
        thread.start();
    }

    private List<Task<I>> createTaskSet() {

        int maxTime = getMaxWaits();

        Map<Integer, List<AnimationImage<I>>> m = new TreeMap<Integer, List<AnimationImage<I>>>();

        // 各gifごとに処理する
        for (AnimationImage<I> image : images) {
            List<Frame<I>> frames = image.getAllFrames();
            int wait = 0;
            int index = 0;
            int length = frames.size();

            log.info("フレームサイズは" + length + "です。");
            // 最大時間を超えるまで
            while (wait < maxTime) {
                Frame<I> frame = frames.get(index);

                List<AnimationImage<I>> task = m.get(wait);
                if (task == null) {
                    task = new ArrayList<AnimationImage<I>>();
                    m.put(wait, task);
                }

                task.add(image);
                wait += frame.getWait();
                index = (index + 1) % length;
            }
        }

        List<Task<I>> ret = new ArrayList<Task<I>>(m.size());

        int delay = 0;
        for (Entry<Integer, List<AnimationImage<I>>> entry : m.entrySet()) {
            delay = entry.getKey() - delay;
            log.info(delay + "msのタスクを設定します。");
            ret.add(new Task<I>(delay, entry.getValue()));
        }
        return ret;
    }

    public void stop() {
        running = false;
        synchronized(this) {
            notify();
        }
        log.info("スレッドを終了します。");
    }

    public void dispose() {
        for (AnimationImage<I> image : images) {
            image.dispose();
        }
    }

    @Override
    public void run() {

        while (running) {
            Task<I> task = taskset.get(index);
            index = (index + 1) % taskset.size();
            synchronized (this) {
                try {
                    log.info(task.getDelay() + "ms待ちます。");
                    if (task.getDelay() > 0) {
                        wait(task.getDelay());
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.info("タスクを実行します。");
            task.execute();
            log.info("リスナに通知します。");
            for (AnimationListener<I> listener : listeners) {
                listener.animate();
            }
        }
    }

    private int getMaxWaits(){
        return LCM.lcm(getWaits());
    }

    private int[] getWaits() {
        int length = images.size();
        int[] ret = new int[length];

        for (int i = 0; i < length; i++) {
            ret[i] = images.get(i).getPeriod();
        }

        System.out.println(Arrays.toString(ret));
        return ret;
    }

    public void addListener(AnimationListener<I> listener) {
        if (running) {
            throw new IllegalStateException();
        }
        listeners.add(listener);
    }

    public void addImage(AnimationImage<I> animationImage) {
        if (running) {
            throw new IllegalStateException();
        }
        images.add(animationImage);
    }

    private static class Task<I> {
        private final int delay;

        private final List<AnimationImage<I>> images;

        private Task(int delay, List<AnimationImage<I>> images) {
            this.delay = delay;
            this.images = images;
        }

        public int getDelay() {
            return delay;
        }

        public void execute() {
            for (AnimationImage<I> image : images) {
                image.next();
            }
        }
    }
}
